#include <stdio.h>

#include <SocketUtils.h>
#include <Thread.h>

#include <string.h>
#include <unistd.h>

#include <FinalCType/String.h>
#include <FinalCType/List.h>

typedef struct {
    char host[100];
    int port;
} HostPort;

createThreadHandle(scanTcpPortHandler) {
    HostPort hostport = {0};
    memcpy(&hostport, data, sizeof(HostPort));

    TcpSocket tcp;
    createTcpSocketWithHostPort(&tcp, hostport.host, hostport.port);

    struct timeval recvout = {0}; // 好像是作为高精度时间，
    recvout.tv_sec = 4;  // 秒 需要
    recvout.tv_usec = 0; // 微秒，不需要
    // 接收超时设置 
    socketSetOption(&tcp, SOL_SOCKET, SO_RCVTIMEO, &recvout, sizeof(recvout));
    socketSetOption(&tcp, SOL_SOCKET, SO_SNDTIMEO, &recvout, sizeof(recvout));
    socketSetOption(&tcp, SOL_SOCKET, SO_ERROR, &recvout, sizeof(recvout));
    
    if (socketConnect(&tcp, &tcp.address, sizeof(tcp.address))) {
        printf("%s:%d - <tcp>      \n", hostport.host, hostport.port);
    }

    close(tcp.socket);
    return NULL;
}

createThreadHandle(scanUdpPortHandler) {
    HostPort hostport = {0};
    memcpy(&hostport, data, sizeof(HostPort));

    UdpSocket udp = {0};
    UdpSocketOrigin origin = {0};
    createUdpSocketWithHostPort(&udp, &origin, hostport.host, hostport.port);

    struct timeval recvout = {0}; // 好像是作为高精度时间，
    recvout.tv_sec = 4;  // 秒 需要
    recvout.tv_usec = 0; // 微秒，不需要
    // 接收超时设置 
    socketSetOption(&udp, SOL_SOCKET, SO_RCVTIMEO, &recvout, sizeof(recvout));
    socketSetOption(&udp, SOL_SOCKET, SO_SNDTIMEO, &recvout, sizeof(recvout));
    socketSetOption(&udp, SOL_SOCKET, SO_ERROR, &recvout, sizeof(recvout));
    
    if (socketConnect(&udp, &udp.address, sizeof(udp.address))) {
        printf("%s:%d - <udp>      \n", hostport.host, hostport.port);
    }
    return NULL;
}

typedef struct ScanType{
    int argc;
    char **argv;

    // [-t|-u] [-s host]
    bool onlyTcp; // -t
    bool onlyUdp; // -u
    bool onlyHost;// -s 
    bool onlyPort;// -p
    bool onlyPortBetween; // -p <start_port>-<end_port>

    char *host; // if -s by host
    int port;   // if -p by port

    int start_port; // if -p by start_port
    int end_port; // if -p by end_port
    // or
    List *ports;

} ScanType;

void checkports(ScanType *type, char *port) {
    String *ports = createStringWith_cstr(port);
    if (stringContains_char(ports, '-')) {
        type->onlyPortBetween = true;
        List *words = stringSplit_cnt(ports, '-', 1);
        if (listLength(words) == 2) {
            type->start_port = atoi(convString(listIndex(words, 0)->data)->value);
            type->end_port =atoi(convString(listIndex(words, 1)->data)->value);
        }
        deleteListWithFunc(words, (void*)free);
    }
    destroyString(ports);
}

void usage() {
    printf("Usage:\tscanport [-t|-u] [-s <host>] [-p <port>-<port>|<port>] [-h]\n\tscanport [<host>|<port>] [<host> <port>]\n");
    exit(0);
}

void ScanHandle(ScanType *type) {
    char handles[][20] = {"-t", "-u", "-s", "-p", "-h"};

    if (type->argc == 1) usage();

    // 前置条件过滤  
        // ./scanport 192.168.1.1
        // ./scanport 5900
    if (type->argc == 2) {

        // ip
        if (stringContains_char(createStringWith_cstr(type->argv[1]), '.')) {
            type->onlyHost = true;
            type->host = type->argv[1];
            return;;
        }

        if ((atoi(type->argv[1]) > 0) && (atoi(type->argv[1]) < 65535)) {
            type->onlyPort = true;
            type->port = atoi(type->argv[1]);
            return;
        }
    }

    // 前置条件过滤  
        // ./scanport 192.168.1.1 5900
    if (type->argc == 3 
        && stringContains_char(createStringWith_cstr(type->argv[1]), '.') 
        && (atoi(type->argv[2]) > 0) && (atoi(type->argv[2]) < 65535)) 
    {
        type->onlyHost = true;
        type->onlyPort = true;

        type->host = type->argv[1];
        type->port = atoi(type->argv[2]);

        checkports(type, type->argv[2]);

        return;
    }


    for (int i = 1; i < type->argc; i++)
    {
        for (int j = 0; j < sizeof(handles)/sizeof(handles[0]); j++)
        {
            String *str = createStringWith_cstr(handles[j]);
            if (stringCompare_cstr(str, type->argv[i])) {
                switch (j) {
                    case 0: // -t
                        type->onlyTcp = true;
                        break;
                    case 1: // -u
                        // type->onlyUdp = true; // 不支持 udp
                        break;
                    case 2: // -s
                        type->onlyHost = true;
                        if (i+1 < type->argc)
                            type->host = type->argv[i+1];
                        else {printf("Unknown Host\n"); exit(1);}
                        break;
                    case 3: // -p
                        type->onlyPort = true;
                        if (i+1 < type->argc)
                            type->port = atoi(type->argv[i+1]);
                        else {printf("Unknown Port\n"); exit(1);}
                        checkports(type, type->argv[i+1]);
                        break;
                    case 4: // -h
                        usage();
                    default:
                        break;
                }
            }
            destroyString(str);
        }
    }
}

// ./scanport [-t|-u] [-s <host>] [-p <port>-<port>|<port>]

int	main(int argc, char **argv)
{
    char progress[4] = {'-','\\','|', '/'};

    ScanType type = { .argc = argc, .argv = argv , .onlyPort = false, .onlyHost = false, .onlyPortBetween = false};

    ScanHandle(&type);

    printf("指定使用 %s 模式\n",type.onlyTcp?"Tcp":type.onlyUdp?"Udp":"Tcp/Udp混合");
    printf("%s主机%s\n",type.onlyHost?"已指定":"未指定",type.onlyHost?type.host:"");


    List *scanPool = NULL;
        
    if (type.onlyPort && type.onlyHost && !type.onlyPortBetween) {
        HostPort *scanItem = malloc(sizeof(HostPort));
        
        strcpy(scanItem->host, type.host);
        scanItem->port = type.port;

        scanPool = listAppend(scanPool, scanItem);
    } else if (type.onlyPort && type.onlyHost && type.onlyPortBetween) {
        for (int port = type.start_port; port < type.end_port; port++)
        {
            HostPort *scanItem = malloc(sizeof(HostPort));
            
            strcpy(scanItem->host, type.host);
            scanItem->port = port;

            scanPool = listAppend(scanPool, scanItem);
        }
    } else {
        if (type.onlyHost && !type.onlyPort) {

            for (int port = 1; port < 65535; port++) {
                HostPort *scanItem = malloc(sizeof(HostPort));
                
                strcpy(scanItem->host, type.host);
                scanItem->port = port;

                printf("准备扫描 %s:%d\r",scanItem->host,scanItem->port);
                scanPool = listAppend(scanPool, scanItem);
            }
        }

        if (!type.onlyHost && type.onlyPort) {
            for (int host = 1; host < 255; host++) {
                HostPort *scanItem = malloc(sizeof(HostPort));
                
                sprintf(scanItem->host, "192.168.1.%d", host);
                scanItem->port = type.port;

                scanPool = listAppend(scanPool, scanItem);
            }
        }
    }

    printf("%s端口",type.onlyPort?"已指定":"未指定");
    if (type.onlyPort && !type.onlyPortBetween) printf(": %d",type.port);
    else printf("区间, 将扫描:%d -> %d", ((HostPort*)listIndex(scanPool, 0)->data)->port, ((HostPort*)listIndex(scanPool, listLength(scanPool)-1)->data)->port);
    printf("\n");

    // for (int host = 1; host < 255; host++)
    List *iter;
    int num = 0;
    list_foreach(iter, scanPool)
    {

        Thread tcpthread;
        createThread(&tcpthread, scanTcpPortHandler, iter->data);
        runThread(&tcpthread);

    // 不可用原因： 无法确定 Udp 扫描方案
        // Thread udpthread;
        // createThread(&udpthread, scanUdpPortHandler, &hostport);
        // runThread(&udpthread);

        printf("%s:%d - %c       \r", ((HostPort*)iter->data)->host, 
                                ((HostPort*)iter->data)->port,
                                progress[num++%4]);
        fflush(stdout);
        usleep(10000);
    }
    
    sleep(2);

    return 0;
}
